{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:30.872288Z",
     "start_time": "2025-01-17T07:28:28.441620Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:33.472936Z",
     "start_time": "2025-01-17T07:28:33.415533Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:18:47.682279100Z",
     "start_time": "2024-07-19T07:18:47.444449800Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:38.296220Z",
     "start_time": "2025-01-17T07:28:38.261761Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:41.427575Z",
     "start_time": "2025-01-17T07:28:41.421491Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:44.732646Z",
     "start_time": "2025-01-17T07:28:44.725570Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 5
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-19T07:18:47.699777900Z",
     "start_time": "2024-07-19T07:18:47.497956400Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n tensor([1.5140]))"
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:28:56.698170Z",
     "start_time": "2025-01-17T07:28:56.694732Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 256\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 7
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "自定义 Layer"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:34:50.988242Z",
     "start_time": "2025-01-17T07:34:50.984594Z"
    }
   },
   "source": [
    "class CustomizedLinear(nn.Module):\n",
    "    def __init__(self, in_features, out_features):\n",
    "        super().__init__()\n",
    "        # 感兴趣的可以在这里实现其他的初始化方法\n",
    "        self.weights = nn.Parameter(torch.zeros(in_features, out_features)) # 定义权重\n",
    "        self.weights = nn.init.xavier_normal_(self.weights) # 使用xavier_normal初始化\n",
    "        self.bias = nn.Parameter(torch.zeros(out_features))  #Parameter是nn.Module的子类，可以被自动求导，可以被优化器优化\n",
    "        \n",
    "    def forward(self, x):\n",
    "        return torch.mm(x, self.weights) + self.bias # 全连接层的前向计算,mm是矩阵乘法"
   ],
   "outputs": [],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:16.672995Z",
     "start_time": "2025-01-17T07:38:16.669970Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            CustomizedLinear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            CustomizedLinear(30, 1)\n",
    "            )\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "source": [
    "model = NeuralNetwork()\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.shape)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:20.360762Z",
     "start_time": "2025-01-17T07:38:20.356997Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "linear_relu_stack.0.weights torch.Size([8, 30])\n",
      "linear_relu_stack.0.bias torch.Size([30])\n",
      "linear_relu_stack.2.weights torch.Size([30, 1])\n",
      "linear_relu_stack.2.bias torch.Size([1])\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NeuralNetwork(\n",
      "  (linear_relu_stack): Sequential(\n",
      "    (0): CustomizedLinear()\n",
      "    (1): ReLU()\n",
      "    (2): CustomizedLinear()\n",
      "  )\n",
      ")\n",
      "--------------------------------------------------\n",
      "Sequential(\n",
      "  (0): CustomizedLinear()\n",
      "  (1): ReLU()\n",
      "  (2): CustomizedLinear()\n",
      ")\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n",
      "ReLU()\n",
      "--------------------------------------------------\n",
      "CustomizedLinear()\n",
      "--------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "for m in model.modules():\n",
    "    print(m)\n",
    "    print('-'*50)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-19T07:19:34.645890800Z",
     "start_time": "2024-07-19T07:19:34.617848300Z"
    }
   }
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:44.193254Z",
     "start_time": "2025-01-17T07:38:44.190420Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:49.053650Z",
     "start_time": "2025-01-17T07:38:49.050915Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 14
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:59.331736Z",
     "start_time": "2025-01-17T07:38:49.073675Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/4600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "779c2d6f94624eb3b65d0d89ddb29800"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 15
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:59.386412Z",
     "start_time": "2025-01-17T07:38:59.332240Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 16
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T07:38:59.403831Z",
     "start_time": "2025-01-17T07:38:59.386412Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3768\n"
     ]
    }
   ],
   "execution_count": 17
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
